package tp.model;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.xml.bind.annotation.XmlRootElement;

/**
 * This class represent a city manager, it can  
 * <ul>
 * 	<li>add a city</li>
 * 	<li>remove a city</li>
 * 	<li>return the list of cities</li>	
 * 	<li>search a city with a given name</li>
 *  <li>search a city at a position</li>
 * 	<li>return the list of cities near 10km of the given position</li>
 * </ul>
 *
 */
@XmlRootElement
public class CityManager {
	
	// ATTRIBUTS
	
	// La distance par défaut autour de laquelle chercher dans la recherche approximative
	private static final int DISTANCE = 10;

	// La liste des villes dans ce cityManager
	private List<City> cities;
	
	// CONSTRUCTEURS
	
	/**
	 * Un gestionnaire de villes sans villes à l'initialisation.
	 */
	public CityManager() {
		this.cities = new LinkedList<City>();
	}
	
	// REQUETES

	/**
	 * @return la liste des villes dans ce gestionnaire.
	 */
	public List<City> getCities() {
		return cities;
	}
	
	/**
	 * @param cityName une portion d'un nom de ville
	 * @return la liste des villes dont le nom contient cityName
	 */
	public List<City> searchFor(String cityName) {
		List<City> res = new ArrayList<City>();
		for (City c : cities) {
			if (c.getName().toUpperCase().contains(cityName.toUpperCase())) {
				res.add(c);
			}
		}
		return res;
	}
	
	/**
	 * @param position la position exacte où chercher la ville
	 * @return la ville aux coordonnées spécifiées
	 * @throws {@link CityNotFound} si aucune ville n'a été trouvée à cet emplacement
	 */
	public City searchExactPosition(Position position) throws CityNotFound {
		for(City city:cities){
			if (position.equals(city.getPosition())){
				return city;
			}
		}
		throw new CityNotFound();
	}
	
	/**
	 * @param position la position autour de laquelle chercher des villes
	 * @return la liste des villes dans un périmètre de DISTANCE km autour de position
	 */
	public CityManager searchNear(Position position) {
		CityManager res = new CityManager();
		for (City c : cities) {
			if (getDistance(position, c.getPosition()) <= DISTANCE) {
				res.addCity(c);
			}
		}
		return res;
	}
	
	// COMMANDES

	/**
	 * Remplace toutes les villes présentes dans ce gestionnaire.
	 * @param cities les villes à mettre en remplacement
	 */
	public void setCities(List<City> cities) {
		this.cities = cities;
	}
	
	/**
	 * Ajoute une ville si elle n'est pas déjà présente.
	 * @param city la ville à ajouter
	 * @return true si l'ajout a bien été fait
	 */
	public boolean addCity(City city) {
		if (!cities.contains(city)) {
			return cities.add(city);
		}
		return false;
	}
	
	/**
	 * Retire une ville de ce gestionnaire si elle y est.
	 * @param city la ville à retirer
	 * @return true si la suppression a eu lieu
	 */
	public boolean removeCity(City city) {
		return cities.remove(city);
	}
	
	// OUTILS
	
	/**
	 * @param p1 la première position
	 * @param p2 la seconde position
	 * @return la distance en kilomètres entre ces deux positions
	 */
	private double getDistance(Position p1, Position p2) {
		double lat1 = p1.getLatitude(),
				lat2 = p2.getLatitude(),
				lon1 = p1.getLongitude(),
				lon2 = p2.getLongitude();
		return 6366 * Math.acos(
					Math.cos(Math.toRadians(lat2))
						* Math.cos(Math.toRadians(lat1))
						* Math.cos(Math.toRadians(lon1) - Math.toRadians(lon2))
					+ Math.sin(Math.toRadians(lat2))
						* Math.sin(Math.toRadians(lat1))
				);
	}
	
}
